/**
 * \file net_lproto.cc
 *
 * \brief kcp + sockets + mbedtls
 */
/*
 *  Copyright (C) 2006-2018, ARM Limited, All Rights Reserved
 *  SPDX-License-Identifier: Apache-2.0
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may
 *  not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  This file is part of lsukm (https://gitee.com/bytes33/lsukm)
 */

#include  "net_lproto.h"
#include  "ikcp.h"

#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#include <windows.h>
#elif !defined(__unix)
#define __unix
#endif

#ifdef __unix
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#endif

/* get system time */
static inline void itimeofday(long *sec, long *usec)
{
#if defined(__unix)
	struct timeval time;
	gettimeofday(&time, NULL);
	if (sec) *sec = time.tv_sec;
	if (usec) *usec = time.tv_usec;
#else
	static long mode = 0, addsec = 0;
	BOOL retval;
	static IINT64 freq = 1;
	IINT64 qpc;
	if (mode == 0) {
		retval = QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
		freq = (freq == 0)? 1 : freq;
		retval = QueryPerformanceCounter((LARGE_INTEGER*)&qpc);
		addsec = (long)time(NULL);
		addsec = addsec - (long)((qpc / freq) & 0x7fffffff);
		mode = 1;
	}
	retval = QueryPerformanceCounter((LARGE_INTEGER*)&qpc);
	retval = retval * 2;
	if (sec) *sec = (long)(qpc / freq) + addsec;
	if (usec) *usec = (long)((qpc % freq) * 1000000 / freq);
#endif
}

/* get clock in millisecond 64 */
static inline IINT64 iclock64(void)
{
	long s, u;
	IINT64 value;
	itimeofday(&s, &u);
	value = ((IINT64)s) * 1000 + (u / 1000);
	return value;
}

static inline IUINT32 iclock()
{
	return (IUINT32)(iclock64() & 0xfffffffful);
}

/* sleep in millisecond */
static inline void lproto_sleep(unsigned long millisecond)
{
#ifdef __unix 	/* usleep( time * 1000 ); */
	struct timespec ts;
	ts.tv_sec = (time_t)(millisecond / 1000);
	ts.tv_nsec = (long)((millisecond % 1000) * 1000000);
	/*nanosleep(&ts, NULL);*/
    usleep((millisecond << 10) - (millisecond << 4) - (millisecond << 3));
#elif defined(_WIN32)
	Sleep(millisecond);
#endif
}

unsigned int lproto_calchash(const char* s1, int len1, const char* s2, int len2)
{
   unsigned int hash = 2166136261;
   int i = 0, j = 0;
   for(i=0; i < len1; i++){
       hash = hash ^ atoi((const char*)s1[i]);
       hash = hash * 16777619;
   } 

   for(j=0; j < len2; j++){
       hash = hash ^ atoi((const char*)s2[j]);
       hash = hash * 16777619;
   } 

   return hash;
}

unsigned short lproto_getport(const struct sockaddr_storage* addr)
{
    unsigned short port = 0;
    if (addr->ss_family == AF_INET) {
        port = ntohs(((struct sockaddr_in*) addr)->sin_port);
    }
    else if (addr->ss_family == AF_INET6) {
        port = ntohs(((struct sockaddr_in6*) addr)->sin6_port);
    }
    return port;
}

int lproto_gethost(const struct sockaddr_storage* addr, char *host, int len)
{
    char thost[INET6_ADDRSTRLEN] = {0x00};
	int nlen = 0;

    if (addr->ss_family == AF_INET) {
        inet_ntop(AF_INET, &(((struct sockaddr_in*)addr)->sin_addr), thost, INET_ADDRSTRLEN);
    }
    else if (addr->ss_family == AF_INET6) {
        inet_ntop(AF_INET6, &(((struct sockaddr_in6*)addr)->sin6_addr), thost, INET6_ADDRSTRLEN);
    }

    nlen = (len < strlen(thost)) ? (len - 1) : strlen(thost);
    strncpy(host, thost, nlen);

    return nlen;
}

/**
 * \brief          create a proto
 *
 * \return         the proto
 */
net_lproto* lproto_create( )
{
    net_lproto *ctx = (net_lproto*)malloc(sizeof(struct net_lproto));
    if (ctx == NULL) return NULL;

    net_init(&ctx->tfd);
    ctx->pkcp = NULL;

    memset(&ctx->local, 0, sizeof(struct sockaddr_storage));
    memset(&ctx->peer, 0, sizeof(struct sockaddr_storage));

    ctx->func_net_event = NULL;
    ctx->func_kcp_event = NULL;
    ctx->func_recv = NULL;

    return ctx;
}

/**
 * \brief          create a kcp
 * \des            the guid + sport 's calc hash value is conv
 */
int lproto_ikcp( net_lproto* ctx, unsigned int conv, int sndwnd, int rcvwnd, 
    int nodelay, int interval, int resend, int nc)
{
    ctx->pkcp = ikcp_create(conv, ctx);
    if (ctx->pkcp == NULL) return ERR_KCP_CREATE_FAILED;

    ikcp_wndsize(ctx->pkcp, sndwnd, rcvwnd);
    ikcp_nodelay(ctx->pkcp, nodelay, interval, resend, nc);

    ctx->pkcp->output = lproto_output;

    return 0;
}

int lproto_connect( net_lproto *ctx, const char *host, const char *port, int proto )
{    
    int ret = net_connect(&ctx->tfd, host, port, proto);
    if (ret == 0)
    {
        socklen_t n = sizeof(struct sockaddr_storage);
        if (getsockname(ctx->tfd.fd, (struct sockaddr*)&ctx->local, &n) !=0 )
        {
            ret = ERR_NET_SOCKET_FAILED;
        }
    }

    return ret;
}

int lproto_accept( net_lproto *ctx, const char *bind, const char *port, int proto )
{
    int ret = 1;
    net_context cfd;

    struct sockaddr_storage addr;
    socklen_t n = sizeof(struct sockaddr_storage);

    while(1)
    {
        net_init(&cfd);
        memset(&addr, 0, n);

        if (ret != 0)
        {
            ret = net_bind(&ctx->tfd, bind, port, proto);
            if (ret == 0) 
            {
                if (getsockname(ctx->tfd.fd, (struct sockaddr*)&ctx->local, &n) !=0 )
                    ret = ERR_NET_SOCKET_FAILED;
            }
            net_usleep(1000000); //1 s
        }

        if (ret == 0)
        {
            ret = net_accept(&ctx->tfd, &cfd, &addr);
            if (ret == 0) 
            {
                net_lproto* nctx = (net_lproto*)malloc(sizeof(net_lproto));

                nctx->tfd = cfd;
                nctx->pkcp = NULL;

                memcpy(&nctx->local, &ctx->local, sizeof(struct sockaddr_storage));
                memcpy(&nctx->peer, &addr, sizeof(struct sockaddr_storage));

                if (ctx->func_net_event)
                    ctx->func_net_event(nctx, LPROTO_EVENT_NEW, ctx);
            }
            else
            {
                /* code */
            }
        }
    }

    return 0;
}

int lproto_output(const char* buf, int len, ikcpcb* kcp, void* user)
{
    net_lproto* ctx = (net_lproto*)user;

    return net_send(&ctx->tfd, buf, len);
}

/**
 * \brief          Read at most 'len' characters. If no error occurs,
 *                 the actual amount read is returned.
 */
int lproto_read( net_lproto *ctx, unsigned char *buf, size_t len )
{
    int ret = 0;
    // no ssl
    if (ctx->pkcp)
    {
        do
        {
            ret = ikcp_recv(ctx->pkcp, (char*)buf, len);
            if (ret > 0) {
                ctx->func_recv(ctx, (const char*)buf, ret);
            }
            else if (ret == -1) {
                ret = 0;
                break;
            }
            else{
                printf(" ikcp_recv err. %d \r\n", ret);
                break;
            }
        } while (1);
    }
    return ret;
}

int lproto_send( net_lproto *ctx, const unsigned char *buf, size_t len )
{
    int ret = 0;
    //no ssl
    if (ctx->pkcp)
    {
        do
        {
            if (ikcp_waitsnd(ctx->pkcp) < 1280)
            {
                ret = ikcp_send(ctx->pkcp, (const char*)buf, len);
                if (ret != 0){
                    printf(" ikcp_send err. %d \r\n", ret);
                }
                break;
            }
            net_usleep(1000); //1 ms
        } while (1);
    }
    return ret;
}

int lproto_loop( net_lproto *ctx )
{
    int ret = 0;
    unsigned char buff[16384];

	while(ctx->tfd.fd > 0)
    {
        ret = net_recv_timeout(&ctx->tfd, buff, 16384, 10);
        if (ret == ERR_NET_TIMEOUT || ret == ERR_NET_WANT_READ)
        {
            ret = 0;
        }
        else
        {
            break;
        }

        //kcp input
        if (ret > 0)
        {
            if (ctx->pkcp)
                ikcp_input(ctx->pkcp, (const char*)buff, ret);
            else if (ctx->func_kcp_event) {
                ctx->func_kcp_event(ctx, LPROTO_EVENT_NEW, (const char*)buff, ret);
            }
        }
        //ssl 

        //read
        lproto_read(ctx, buff, 16384);

        //kcp update
        if (ctx->pkcp)
            ikcp_update(ctx->pkcp, iclock());

        ret = 0;
    }

    return 0;
}

void lproto_free( net_lproto *ctx)
{
    //sock
    net_free(&ctx->tfd);

    //kcp
    if (ctx->pkcp)
        ikcp_release(ctx->pkcp);
    
    //ssl

    free((void*)ctx);
}